use std::fs;
use std::collections::{HashSet};
use sysinfo::{SystemExt, System};
use std::os::unix::io::AsRawFd;
use tabled::{Tabled, Table, Style, Footer};
use proc_maps::get_process_maps;
use libc;

// get PAGESIZE
// const PAGESIZE: usize = 4096


// 根据memory 大小，打印不同的单位
// memory 的默认单位是 KB 
fn display_size(memory: &u64) -> String {
    let kb = 1024;
    let mb = 1024 * kb;
    let gb = 1024 * mb;
    if *memory > gb {
        format!("{:.2} GB", *memory as f64 / gb as f64)
    } else if *memory > mb {
        format!("{:.2} MB", *memory as f64 / mb as f64)
    } else if *memory > kb {
        format!("{:.2} KB", *memory as f64 / kb as f64)
    } else {
        format!("{}", *memory)
    }
}

fn display_filename(filename: &String) -> String {
    let offset=50;
    let len=filename.len();
    if len > offset {
        format!("{:?}", &filename[(len-offset)..].to_string())
    } else {
        format!("{:?}", filename)
    }
}
fn display_percent(percent: &f64) -> String {
    format!("{:3.2}", percent)
}


#[derive(Debug, Tabled, Clone)]
#[allow(dead_code)]
pub struct Fcache {
    
    #[header("文件名")]
    #[field(display_with="display_filename")]
    filename: String,

    #[header("文件大小")]
    #[field(display_with = "display_size")]
    size: u64,

    #[header("缓存大小")]
    #[field(display_with = "display_size")] 
    cached: u64,

    #[header("缓存百分比")]
    #[field(display_with = "display_percent")] 
    percent: f64,
}

impl Fcache {
    fn new(filename:String, size:u64, cached:u64, percent:f64) -> Fcache {
        Fcache {
            filename: filename,
            size: size,
            cached: cached,
            percent: percent,
        }
    }
}


//获取所有/proc/pid/maps文件中的文件列表
fn get_all_process_files() -> HashSet<String> {
    let s = System::new_all();
    let mut files = HashSet::new();
    for (pid, _) in s.processes() {
        for file in get_files_by_pid(pid) {
            files.insert(file);
        }
    }
    files
}

fn sysinfo_pid_to_i32(pid: sysinfo::Pid) -> i32 {
    let pid_i32: i32 = pid.into();
    pid_i32
}

//解析/proc/pid/maps文件返回文件列表
fn get_files_by_pid(pid: &sysinfo::Pid) ->Vec<String>{
    //通过 libc::mmap 和 libc::mincore 检查当前进程file文件的 page 页面是否驻留在内存中
    let mut files:Vec<String> = Vec::new();
    let maps = get_process_maps(sysinfo_pid_to_i32(*pid)).unwrap();
    for map in maps {
        if map.filename() == None {
            continue;
        }
        if ! map.filename().unwrap().to_str().unwrap().to_string().contains("/") {
            continue;
        }
        if map.filename().unwrap().to_str().unwrap().to_string().contains("/dev") {
            continue;
        }
        if fs::metadata(map.filename().unwrap().to_str().unwrap()).is_ok() {
            files.push(map.filename().unwrap().to_str().unwrap().to_string())
        }
    }
    files
}

fn get_file_cache_info(filename: String) -> Fcache {
    
    let fd = fs::File::open(filename.clone()).unwrap();
    let fd_raw = fd.as_raw_fd();
    let page_size = 4096;
    let fsize = fs::metadata(filename.clone()).unwrap().len();
    let page_count = (fsize + page_size - 1) / page_size;
    let mut pages = vec![0; page_count as usize];
    
    if fsize == 0 { 
        Fcache::new(filename.clone(), 0, 0, 0.0 )    
    } else {
        unsafe {
            let page_ptr = libc::mmap(
                0 as *mut libc::c_void,
                fsize as libc::size_t,
                libc::PROT_READ,
                libc::MAP_SHARED,
                fd_raw,
                0,
            );
            if page_ptr < 0 as *mut libc::c_void {
                panic!("mmap failed");
            }
            let ret = libc::mincore(page_ptr, fsize as usize, &mut pages[0]);
            if ret == -1 {
                panic!("mincore failed");
            }
            libc::munmap(page_ptr, fsize as usize);
        } 
        
        let mut cache_count = 0;
        for page in pages {
            if page == 1 {
                cache_count += 1;
            }
        }
        
        Fcache::new(filename.clone(), fsize, cache_count*page_size, (cache_count as f64 / page_count as f64) * 100.0 )
    }
}

pub fn get_all_fcache_by_pid(pid: &sysinfo::Pid) -> Vec<Fcache> {
    let mut all_fcache:Vec<Fcache> = Vec::new();
    let files = get_files_by_pid(pid);

    // files 去重
    let mut files_set = HashSet::new();
    for file in files {
        files_set.insert(file);
    }

    for file in files_set {
        let _tmp = get_file_cache_info(file);
        if _tmp.cached > 0 {
            all_fcache.push(_tmp);
        }
    }
     // all_fcache按照 cache排序，从大到小
     all_fcache.sort_by(|a, b| b.cached.cmp(&a.cached));
     all_fcache
}

pub fn get_all_file_cached()-> Vec<Fcache> {

    let mut all_fcache=Vec::new();
    for filename in get_all_process_files() {
        let _tmp = get_file_cache_info(filename);
        if _tmp.cached > 0 {
            all_fcache.push(_tmp);
        }        
    }

    // all_fcache按照 cache排序，从大到小
    all_fcache.sort_by(|a, b| b.cached.cmp(&a.cached));
    all_fcache
}

pub fn print_fcache_vector(fcaches: &[Fcache])
{
    let footer = format!("文件总数 {}\n缓存总和 {}",
    fcaches.len(),
    display_size(&sum_memory(&fcaches)));

    // tabled 打印 
    let table = Table::new(fcaches)
        .with(Style::dots())
        .with(Footer(footer))
        .to_string();
    println!("{}", table); 
}

// 获取所有进程 memory 总和
fn sum_memory(fcaches: &[Fcache]) -> u64 {
    let mut sum = 0;
    for fcache in fcaches {
        sum += fcache.cached;
    }
    sum
}
